feat: support .bicepparam parameter files for Bicep deployments#253
feat: support .bicepparam parameter files for Bicep deployments#253bgdnext64 wants to merge 3 commits into
Conversation
Detect .bicepparam extension on --parametersFilePath and automatically compile to ARM JSON format using 'bicep build-params' before deployment. Both .json and .bicepparam formats are now supported. - Add .bicepparam detection and compilation in bicepCmd.go - Add storage-account-simple-params.bicepparam sample file - Add TestBicepWithBicepparamFile e2e test - Update known-issues doc (no longer unsupported) - Add .bicepparam example to quickstart and flags docs Closes #12
- Bump Go 1.26.1 to 1.26.2 to fix stdlib vulnerabilities (govulncheck) - Fix errcheck: handle os.Remove return value in e2eBicep_test.go - Fix markdown: add missing blank lines before headings
There was a problem hiding this comment.
Pull request overview
Adds first-class support for Bicep-native .bicepparam parameter files in the MPF Bicep workflow by compiling them to ARM JSON parameters via bicep build-params when provided through --parametersFilePath, and updates samples/docs/tests accordingly (closes #12).
Changes:
- Detect
.bicepparamincmd/bicepCmd.goand compile to.parameters.jsonbefore running the deployment authorization checker. - Add a
.bicepparamsample and an e2e test exercisingbicep build-params+ deployment with the compiled parameters. - Update documentation to reflect
.bicepparamsupport and provide quickstart/flags examples.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
cmd/bicepCmd.go |
Adds .bicepparam detection and invokes bicep build-params to generate ARM JSON parameters prior to deployment. |
e2eTests/e2eBicep_test.go |
Adds a new e2e test using a .bicepparam file (compiled to JSON) with a simple storage account Bicep sample. |
samples/bicep/storage-account-simple-params.bicepparam |
Adds a Bicep-native parameters file sample referencing the storage account template. |
docs/known-issues-and-workarounds.MD |
Updates known-issues to reflect that .bicepparam is now supported. |
docs/installation-and-quickstart.md |
Adds quickstart examples showing .bicepparam usage (currently has formatting/command issues). |
docs/commandline-flags-and-env-variables.md |
Updates flag documentation to note .json and .bicepparam are supported. |
go.mod |
Bumps the Go toolchain version referenced by the module. |
| compiledParamsPath := strings.TrimSuffix(flgParametersFilePath, filepath.Ext(flgParametersFilePath)) + ".parameters.json" | ||
| buildParamsCmd := exec.Command(flgBicepExecPath, "build-params", flgParametersFilePath, "--outfile", compiledParamsPath) | ||
| buildParamsCmd.Dir = filepath.Dir(flgParametersFilePath) | ||
|
|
||
| output, err := buildParamsCmd.CombinedOutput() | ||
| if err != nil { | ||
| log.Fatalf("error running bicep build-params: %s\n%s", err, string(output)) | ||
| } | ||
| log.Infoln("Bicep parameters compiled successfully, ARM Parameters JSON created at:", compiledParamsPath) |
There was a problem hiding this comment.
The compiled .parameters.json file created from a .bicepparam input is never deleted, unlike the generated ARM template which is cleaned up later. This can leave artifacts next to the user’s parameters file (and can dirty a repo working tree). Track whether compilation happened and remove the compiled file at the end (or write it to a temp file and clean up via defer).
| compiledParamsPath := strings.TrimSuffix(flgParametersFilePath, filepath.Ext(flgParametersFilePath)) + ".parameters.json" | |
| buildParamsCmd := exec.Command(flgBicepExecPath, "build-params", flgParametersFilePath, "--outfile", compiledParamsPath) | |
| buildParamsCmd.Dir = filepath.Dir(flgParametersFilePath) | |
| output, err := buildParamsCmd.CombinedOutput() | |
| if err != nil { | |
| log.Fatalf("error running bicep build-params: %s\n%s", err, string(output)) | |
| } | |
| log.Infoln("Bicep parameters compiled successfully, ARM Parameters JSON created at:", compiledParamsPath) | |
| tempParamsFile, err := os.CreateTemp("", "*.parameters.json") | |
| if err != nil { | |
| log.Fatalf("error creating temporary ARM parameters file: %v", err) | |
| } | |
| compiledParamsPath := tempParamsFile.Name() | |
| if err := tempParamsFile.Close(); err != nil { | |
| _ = os.Remove(compiledParamsPath) | |
| log.Fatalf("error closing temporary ARM parameters file: %v", err) | |
| } | |
| buildParamsCmd := exec.Command(flgBicepExecPath, "build-params", flgParametersFilePath, "--outfile", compiledParamsPath) | |
| buildParamsCmd.Dir = filepath.Dir(flgParametersFilePath) | |
| output, err := buildParamsCmd.CombinedOutput() | |
| if err != nil { | |
| _ = os.Remove(compiledParamsPath) | |
| log.Fatalf("error running bicep build-params: %s\n%s", err, string(output)) | |
| } | |
| defer func(path string) { | |
| if err := os.Remove(path); err != nil && !os.IsNotExist(err) { | |
| log.Warnf("error removing temporary ARM parameters file %q: %v", path, err) | |
| } | |
| }(compiledParamsPath) | |
| log.Infoln("Bicep parameters compiled successfully, temporary ARM Parameters JSON created at:", compiledParamsPath) |
There was a problem hiding this comment.
Done. Refactored to os.CreateTemp so the compiled JSON goes to a temp path instead of next to the user's .bicepparam file, and added a defer that removes it (with a warning on cleanup error). The compile logic is now in a small helper, bicepUtils.CompileBicepParamsToTempFile, which also handles error cleanup so the temp file isn't left behind on failure.
| .\azmpf.exe bicep --bicepFilePath .\samples\bicep\storage-account-simple.bicep --parametersFilePath .\samples\bicep\storage-account-simple-params.json --jsonOutput --verbose | ||
| ```#### Bicep with .bicepparam Parameters File | ||
|
|
There was a problem hiding this comment.
Markdown formatting is broken here: the PowerShell code block is not properly closed before the next heading (the closing fence and heading are on the same line). This will render incorrectly in the docs; put the closing ``` on its own line, then add the heading on the next line.
There was a problem hiding this comment.
Fixed. Moved the closing fence to its own line and put a blank line before the next heading so the markdown renders correctly.
| $env:MPF_SPOBJECTID = "YOUR_SP_OBJECT_ID" | ||
| $env:MPF_BICEPEXECPATH = (Get-Command bicep).Source | ||
|
|
||
| .\.azmpf.exe bicep --bicepFilePath .\samples\bicep\storage-account-simple.bicep --parametersFilePath .\samples\bicep\storage-account-simple-params.bicepparam --jsonOutput --verbose |
There was a problem hiding this comment.
This PowerShell example command has an extra dot in the executable path (.\.azmpf.exe). It should match the earlier examples (e.g., ./azmpf on bash, .\azmpf.exe on Windows) so users can copy/paste successfully.
| .\.azmpf.exe bicep --bicepFilePath .\samples\bicep\storage-account-simple.bicep --parametersFilePath .\samples\bicep\storage-account-simple-params.bicepparam --jsonOutput --verbose | |
| .\azmpf.exe bicep --bicepFilePath .\samples\bicep\storage-account-simple.bicep --parametersFilePath .\samples\bicep\storage-account-simple-params.bicepparam --jsonOutput --verbose |
There was a problem hiding this comment.
Fixed — .\.azmpf.exe was a typo, it's now .\azmpf.exe to match the other examples and so it can be copy/pasted directly.
| bicepFilePath, _ = getAbsolutePath(bicepFilePath) | ||
| parametersFilePath, _ = getAbsolutePath(parametersFilePath) | ||
|
|
There was a problem hiding this comment.
The errors from getAbsolutePath are ignored here. If path resolution fails, the test will proceed with a bad path and fail later with a less actionable error. Handle the error and fail/skip the test accordingly.
| bicepFilePath, _ = getAbsolutePath(bicepFilePath) | |
| parametersFilePath, _ = getAbsolutePath(parametersFilePath) | |
| bicepFilePath, err = getAbsolutePath(bicepFilePath) | |
| if err != nil { | |
| t.Fatalf("failed to resolve absolute path for bicep file %q: %v", "../samples/bicep/storage-account-simple.bicep", err) | |
| } | |
| parametersFilePath, err = getAbsolutePath(parametersFilePath) | |
| if err != nil { | |
| t.Fatalf("failed to resolve absolute path for parameters file %q: %v", "../samples/bicep/storage-account-simple-params.bicepparam", err) | |
| } |
There was a problem hiding this comment.
Done. Both getAbsolutePath calls now check the error and t.Fatalf with a descriptive message instead of silently continuing with a possibly-bogus path.
| // Compile .bicepparam to ARM JSON parameters | ||
| compiledParamsPath := strings.TrimSuffix(parametersFilePath, filepath.Ext(parametersFilePath)) + ".parameters.json" | ||
| buildParamsCmd := exec.Command(bicepExecPath, "build-params", parametersFilePath, "--outfile", compiledParamsPath) | ||
| buildParamsCmd.Dir = filepath.Dir(parametersFilePath) |
There was a problem hiding this comment.
This test compiles the .bicepparam file manually and passes the compiled JSON into MPF, so it doesn’t actually exercise the new behavior added in cmd/bicepCmd.go (auto-detect + compile when --parametersFilePath ends with .bicepparam). Consider adding a CLI-level e2e that runs azmpf bicep --parametersFilePath <.bicepparam> (or factoring the compilation logic into a testable helper) to verify the intended user-facing path.
There was a problem hiding this comment.
Good point. I extracted the compile logic out of cmd/bicepCmd.go into pkg/infrastructure/bicepUtils (IsBicepParamFile + CompileBicepParamsToTempFile). The CLI now calls the helper, and the e2e test calls the same helper instead of doing its own bicep build-params invocation, so the test exercises the actual user-facing code path. Also added a small unit test for IsBicepParamFile.
| _, err = bicepCmd.CombinedOutput() | ||
| if err != nil { | ||
| log.Error(err) | ||
| t.Error(err) |
There was a problem hiding this comment.
If bicep build fails, the test logs/t.Errorf but continues, which can lead to follow-on failures (missing template file) that obscure the root cause. Use t.Fatal/t.Fatalf here to stop the test when the build fails.
| _, err = bicepCmd.CombinedOutput() | |
| if err != nil { | |
| log.Error(err) | |
| t.Error(err) | |
| output, err = bicepCmd.CombinedOutput() | |
| if err != nil { | |
| log.Errorf("error running bicep build: %s\n%s", err, string(output)) | |
| t.Fatalf("error running bicep build: %s\n%s", err, string(output)) |
There was a problem hiding this comment.
Done. Switched to t.Fatalf (with the bicep build output included) so a build failure stops the test immediately instead of cascading into a missing-template error later.
| t.Cleanup(func() { _ = os.Remove(compiledParamsPath) }) | ||
|
|
||
| // Build bicep to ARM template | ||
| armTemplatePath := strings.TrimSuffix(bicepFilePath, ".bicep") + ".json" |
There was a problem hiding this comment.
armTemplatePath is generated during the test but never removed. Add a t.Cleanup (or defer) to delete it to avoid leaving artifacts under samples/ and to prevent interference across repeated e2e runs.
| armTemplatePath := strings.TrimSuffix(bicepFilePath, ".bicep") + ".json" | |
| armTemplatePath := strings.TrimSuffix(bicepFilePath, ".bicep") + ".json" | |
| t.Cleanup(func() { _ = os.Remove(armTemplatePath) }) |
There was a problem hiding this comment.
Done. Added t.Cleanup(func() { _ = os.Remove(armTemplatePath) }) right after computing the path so the generated .json is removed even if the test fails or panics.
maniSbindra
left a comment
There was a problem hiding this comment.
Review findings not yet raised by the automated review.
| if strings.HasSuffix(strings.ToLower(flgParametersFilePath), ".bicepparam") { | ||
| log.Infoln("Detected .bicepparam file, compiling to ARM parameters JSON format") | ||
| compiledParamsPath := strings.TrimSuffix(flgParametersFilePath, filepath.Ext(flgParametersFilePath)) + ".parameters.json" | ||
| buildParamsCmd := exec.Command(flgBicepExecPath, "build-params", flgParametersFilePath, "--outfile", compiledParamsPath) |
There was a problem hiding this comment.
Silent overwrite of existing file
compiledParamsPath is derived deterministically as <name>.parameters.json next to the original .bicepparam file. If a file with that name already exists (e.g. the user already has a storage-account-simple-params.parameters.json), it is silently overwritten without warning. Consider checking for file existence first and erroring out .
There was a problem hiding this comment.
Addressed. The compiled parameters file now goes to os.CreateTemp("", "mpf-bicepparam-*.parameters.json") instead of being placed next to the source file, so any pre-existing <name>.parameters.json the user has is left untouched. The compiled file is removed via defer when MPF exits.
| // Microsoft.Storage/storageAccounts/read | ||
| // Microsoft.Storage/storageAccounts/write | ||
| assert.NotEmpty(t, mpfResult.RequiredPermissions) | ||
| assert.GreaterOrEqual(t, len(mpfResult.RequiredPermissions[mpfConfig.SubscriptionID]), 4) |
There was a problem hiding this comment.
Assertion too weak — we need to use exact permission count
If there a good reason not to do so then we need to mention it.
assert.GreaterOrEqual(..., 4) is imprecise. The PR comment above lists exactly 4 expected permissions, so the exact count is known. The existing TestBicepAksFullDeployment uses assert.Equal(t, 9, ...) for the same reason — an exact count catches regressions that a lower-bound check misses. Please use assert.Equal(t, 4, ...) to stay consistent and make the test deterministic.
Thanks
There was a problem hiding this comment.
Done. Switched to assert.Equal(t, 4, len(...)) to match the established pattern in TestBicepAksFullDeployment and to actually catch regressions in the permission count.
maniSbindra
left a comment
There was a problem hiding this comment.
Hi, a few changes in addition to the automated review requested. Thanks, Mani
…eanup, docs fixes, exact assertion count
|
Quick update on testing: I ran the new Result: PASS in about 120 seconds. The auto-compile path via the new Also worth flagging: the failing CI checks here look like the same upstream |
Detect .bicepparam extension on --parametersFilePath and automatically compile to ARM JSON format using 'bicep build-params' before deployment. Both .json and .bicepparam formats are now supported.
Closes #12